package mredis

import (
	"net"
	"time"
	"bufio"
	"fmt"
	"errors"
	"strconv"
	"io"
	"io/ioutil"
	"strings"
	"bytes"
)

var RedisKeyNotExist =  RedisError("Redis key does not exist ")

type RedisConn struct {
	conn net.Conn	//一个网络连接
	connTime time.Time //建立连接的时间
	db int
	reader       *bufio.Reader
	writer       *bufio.Writer
}
//Redis错误
type RedisError string
func (err RedisError) Error() string { return "Redis Error: " + string(err) }



func (c *RedisConn) Close() error {
	return c.conn.Close()
}
func (c *RedisConn) WriteString(s string) (int,error) {
	i,err := c.writer.WriteString(s)
	if err != nil {
		return i,err
	}
	err = c.writer.Flush()
	return i,err
}
func (c *RedisConn) WriteStringFormat(format string, a ...interface{})(int,error)  {
	s := fmt.Sprintf(format,a...)

	i,err := c.writer.WriteString(s)
	if err != nil {
		return i,err
	}
	err = c.writer.Flush()
	return i,err
}

// 读取一行响应值
func (c *RedisConn) readLine() ([]byte,error) {
	line, isPrefix, err := c.reader.ReadLine()
	if err != nil {
		return nil, err
	}

	if isPrefix {
		return nil, bufio.ErrBufferFull
	}
	//fmt.Println(string(line))
	if len(line) == 0 {
		return nil, RedisError("redis: reply is empty")
	}
	if isNull(line) {
		return nil, nil
	}
	return line, nil
}

// 读取一行返回值，并作为数字处理
func (c *RedisConn) ReadAsInt() (int64,error){

	line, err := c.readLine()
	if err != nil {
		return 0, err
	}
	switch line[0] {
	case REDIS_REPLY_ERROR:
		return 0, errors.New(string(line[1:]))
	case REDIS_REPLY_INT:
		return strconv.ParseInt(string(line[1:]), 10, 32)
	default:
		return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line)
	}
}
// 将返回值当做字符串处理
func (c *RedisConn) ReadAsString()(string,error){
	line,err := c.readLine()
	if err != nil {
		return "",err
	}
	if len(line) > 0 {
		if line[0] == REDIS_REPLY_ERROR {
			return "", errors.New(string(line[1:]))
		}
		if line[0] == REDIS_REPLY_STRING {
			l, err := strconv.Atoi(string(line[1:]))
			if err != nil {
				return "", err
			}

			buf := bytes.NewBufferString("")
			for l > 0 {
				b, err := c.reader.ReadByte()
				if err != nil {
					return "", err
				}
				buf.WriteByte(b)
				l--
			}

			return buf.String(), nil
		}
		if line[0] == REDIS_REPLY_OK {
			return string(line[1:]), nil
		}

	}else{
		return "",RedisReplyEmptyError
	}
	return "", fmt.Errorf("redis: can't parse string reply: %.100q", line)
}

func (c *RedisConn) ReadAsArray()([]string,error){
	b,err := c.readLine()
	if err != nil {
		return nil,err
	}
	if b[0] == REDIS_REPLY_ERROR {
		return nil,RedisError(string(b[1:]))
	}
	if b[0] == REDIS_REPLY_ARRAY {
		size, err := strconv.Atoi(strings.TrimSpace(string(b[1:])))
		if err != nil {
			return nil, RedisError("MultiBulk reply expected a number")
		}
		if size <= 0 {
			return make([]string, 0), nil
		}
		res := make([]string, size)

		for i := 0; i < size; i++ {
			s, err := readResponse(c.reader, "")
			if err == RedisKeyNotExist {
				continue
			}
			if err != nil {
				return nil, err
			}
			res[i] = string(s)
		}
		return res, nil
	}
	return nil,RedisError("Unexpected response to " + string(b))
}

func readResponse(r *bufio.Reader,line string) ([]byte,error){
	var err error
	if line == ""{
		line,err = r.ReadString('\n')
		if err != nil {
			return nil,err
		}
	}
	switch line[0] {
	case REDIS_REPLY_INT:
		return []byte(strings.TrimSpace(line[1:])),nil
	case REDIS_REPLY_STRING:
		size, err := strconv.Atoi(strings.TrimSpace(line[1:]))
		if err != nil {
			return nil, err
		}
		if size == -1 {
			return nil, RedisKeyNotExist
		}
		lr := io.LimitReader(r, int64(size))
		b, err := ioutil.ReadAll(lr)
		if err == nil {
			_, err = r.ReadString('\n')
		}
		return b,err
	case REDIS_REPLY_ARRAY:
		size, err := strconv.Atoi(strings.TrimSpace(string(line[1:])))
		if err != nil {
			return nil, RedisError("MultiBulk reply expected a number")
		}
		if size <= 0 {
			return make([]byte, 0), nil
		}
		buf := bytes.NewBufferString("")

		for i := 0; i < size; i++ {
			s, err := readResponse(r, "")
			if err == RedisKeyNotExist {
				continue
			}
			if err != nil {
				return nil, err
			}
			buf.Write(s)
		}
		return buf.Bytes(), nil
	case REDIS_REPLY_OK:
		return []byte(strings.TrimSpace(line[1:])),nil
	default:
		return nil, RedisError("Expecting Prefix '$' or ':'" + line)
	}
}


func isNull(b []byte) bool {
	return len(b) == 3 && (b[0] == REDIS_REPLY_STRING || b[0] == REDIS_REPLY_ARRAY) && b[1] == REDIS_REPLY_ERROR && b[2] == '1'
}

